package bancosys.tec.persist.validator;

import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import bancosys.tec.persist.Persistable;

/**
 * @author Fabio Sakiyama
 * @created 27/04/2011
 * @param <T> tipo de bean
 */
public final class CompositeValidator<T extends Persistable> implements Validator<T> {

    private final Class<T> targetClass;

    private Set<Validator<T>> validators = new LinkedHashSet<Validator<T>>();

    /**
     * Construtor
     * 
     * @param targetClass targetClass
     */
    public CompositeValidator(Class<T> targetClass) {
        this.targetClass = targetClass;
    }

    /**
     * {@inheritDoc}
     */
    public Class<? extends T> getTargetClass() {
        return this.targetClass;
    }

    /**
     * Método que adiciona um validator {@link Validator}na lista de validators.
     * 
     * @param validator BeanValidator.
     */
    public void addValidator(Validator<T> validator) {
        this.validators.add(validator);
    }

    /**
     * Método que adiciona todos os validators {@link Validator} na lista de validators.
     * 
     * @param validator Validator.
     */
    public void addAllValidator(Set<Validator<T>> validator) {
        this.validators.addAll(validator);
    }

    /**
     * {@inheritDoc}
     */
    public List<ValidationError> validateInsert(T bean) {
        List<ValidationError> errors = null;
        for (Validator<T> validator : this.validators) {
            errors = this.sumErrors(errors, validator.validateInsert(bean));
        }
        return errors;
    }

    /**
     * {@inheritDoc}
     */
    public List<ValidationError> validateUpdate(T bean) {
        List<ValidationError> errors = null;
        for (Validator<T> validator : this.validators) {
            errors = this.sumErrors(errors, validator.validateUpdate(bean));
        }
        return errors;
    }

    /**
     * {@inheritDoc}
     */
    public List<ValidationError> validateRemove(T bean) {
        List<ValidationError> errors = null;
        for (Validator<T> validator : this.validators) {
            errors = this.sumErrors(errors, validator.validateRemove(bean));
        }
        return errors;
    }

    /**
     * {@inheritDoc}
     */
    public List<ValidationError> validateInsertAuthorization(T bean) {
        List<ValidationError> errors = null;
        for (Validator<T> validator : this.validators) {
            errors = this.sumErrors(errors, validator.validateInsertAuthorization(bean));
        }
        return errors;
    }

    /**
     * {@inheritDoc}
     */
    public List<ValidationError> validateUpdateAuthorization(T bean) {
        List<ValidationError> errors = null;
        for (Validator<T> validator : this.validators) {
            errors = this.sumErrors(errors, validator.validateUpdateAuthorization(bean));
        }
        return errors;
    }

    /**
     * {@inheritDoc}
     */
    public List<ValidationError> validateRemoveAuthorization(T bean) {
        List<ValidationError> errors = null;
        for (Validator<T> validator : this.validators) {
            errors = this.sumErrors(errors, validator.validateRemoveAuthorization(bean));
        }
        return errors;
    }

    /**
     * Dadas duas listas, se uma delas for <code>null</code>, devolve a outra, se ambas forem <code>null</code> devolve <code>null</code>,
     * se ambas forem diferentes de <code>null</code> devolve uma terceira lista com o conteudo das duas.
     * 
     * @param errors lista 1
     * @param moreErrors lista 2
     * @return soma das duas listas ou <code>null</code>.
     */
    protected List<ValidationError> sumErrors(final List<ValidationError> errors, final List<ValidationError> moreErrors) {
        if ((errors == null) && (moreErrors == null)) {
            return null;
        }
        if ((errors != null) && (moreErrors == null)) {
            return errors;
        }
        if ((errors == null) && (moreErrors != null)) {
            return moreErrors;
        }
        this.validateAddError(errors, moreErrors);
        return errors;
    }

    /**
     * Valida se a mensagem de erro já esta na lista, para que não sejam incluídas mensagens duplicadas
     * 
     * @param errors - lista com as mensagens de erros já gravadas
     * @param error - nova mensagem de erro, para ser verificado
     * @return lista com as mensagens de erro
     */
    private List<ValidationError> validateAddError(final List<ValidationError> errors, final ValidationError error) {
        for (final ValidationError validationError : errors) {
            final String msgErro = validationError.getMessage();
            if (msgErro.equals(error.getMessage())) {
                return errors;
            }
        }
        errors.add(error);
        return errors;
    }

    /**
     * Adiciona uma Lista de Erros em outra. Validando se não estão sendo incluídos erros duplicados
     * 
     * @param errors - Lista principal, os erros da outra lista serão incluídos nesta.
     * @param moreErrors - Lista que vai ser adicionada na outra lista.
     * @return Lista com todos os erros não duplicados.
     */
    private List<ValidationError> validateAddError(final List<ValidationError> errors, final List<ValidationError> moreErrors) {
        for (final ValidationError error : moreErrors) {
            this.validateAddError(errors, error);
        }
        return errors;
    }

    /**
     * @return the validators
     */
    public Set<Validator<T>> getValidators() {
        return this.validators;
    }
}
